﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
using System.Windows.Input;
using System.ComponentModel;

using ObjectEditor;
using ObjectEditor.Display3D;
using ObjectEditor.Editor;
using Microsoft.Win32;
using Microsoft.Practices.Unity;
using HelixToolkit.Wpf;
using EQEmu.Pathing;
using EQEmu.Pathing.Display3D;

namespace EQEmu.PathingPlugin
{
    internal class PathingVisual3D : ModelVisual3D
    {        
    }

    public class PathingRibbonTabViewModel : ViewModel, IWorldModel3DProvider, IObjectEditor, IDisposable
    {
#if DEBUG
        private static int _creationCount = 0;
#endif

        public event EventHandler<ModelChangedEventArgs> ModelChanged;
        public event EventHandler<ObjectSelectedEventArgs> ObjectSelected;
        public event EventHandler<CameraChangeEventArgs> RequestCameraChange;

        private Path _pathing;
        private PathDisplay3D _pathing3D;
        private ModelVisual3D _modelVisual;
        private Node _selectedNode;
        private IEnumerable<Node> _multipleSelections;
        private double _zAdjustment = 2.0;
        private bool _autoConnect = true;
        private bool _warpConnect = false;
        private bool _twoWayConnect = true;

        private DelegateCommand _newFileCommand;
        private DelegateCommand _openFileCommand;
        private DelegateCommand _saveFileCommand;
        private DelegateCommand _deleteNodeCommand;
        private DelegateCommand _createNodeCommand;        

        private string _currentFileName = String.Empty;
        private Transform3D _transform;

        private BoxVisual3D _cursor = new BoxVisual3D()
        {
            Width = 5,
            Length = 5,
            Height = 0.1,
            Material = Materials.Blue
        };

        public PathingRibbonTabViewModel()
        {
#if DEBUG
            _creationCount++; 
#endif
        }

        public ModelVisual3D Model3D
        {
            get { return _modelVisual; }
            private set
            {
                if (_modelVisual != null)
                {
                    _modelVisual.Children.Clear();
                }

                _modelVisual = value;
                OnPropertyChanged("Model3D");                
                OnModelChanged();
            }
        }

        protected void OnObjectSelected(INotifyPropertyChanged obj,IEnumerable<INotifyPropertyChanged> all=null)
        {
            var e = ObjectSelected;            
            if (e != null)
            {
                e(this, new ObjectSelectedEventArgs(obj,all));
            }
        }

        protected void OnModelChanged()
        {
            var e = ModelChanged;
            if (e != null)
            {
                e(this, new ModelChangedEventArgs());
            }
        }

        protected void OnCameraChange(CameraChangeEventArgs change)
        {
            var e = RequestCameraChange;
            if (e != null)
            {
                e(this, change);
            }
        }



        public static string PathingFileExtension
        {
            get
            {
                return "path";
            }
        }

        public static string PathingFileFilter
        {
            get
            {
                return String.Format("Pathing Files (.{0})|*.{0}|All Files (*.*)|*.*",PathingFileExtension);
            }
        }

        public string CurrentFileName
        {
            get
            {
                return _currentFileName;
            }
            set
            {
                _currentFileName = value;                
                OnPropertyChanged("CurrentFileName");
            }
        }

        private void InitPathingData()
        {
            if (PathingData != null)
            {
                PathingData.NodeAdded += PathingData_NodeAdded;
                PathingData.NodeRemoved += PathingData_NodeRemoved;
            }
        }

        void PathingData_NodeRemoved(object sender, PathModifiedEventArgs e)
        {
            OnPropertyChanged("Nodes");
            OnPropertyChanged("DisconnectedNodes");
        }

        void PathingData_NodeAdded(object sender, PathModifiedEventArgs e)
        {
            OnPropertyChanged("Nodes");
            OnPropertyChanged("DisconnectedNodes");
        }

        private void DisposePathingData()
        {
            if (PathingData != null)
            {
                PathingData.NodeRemoved -= PathingData_NodeRemoved;
                PathingData.NodeAdded -= PathingData_NodeAdded;
            }
        }

        public Pathing.Path PathingData
        {
            get { return _pathing; }
            private set
            {
                SelectedNode = null;

                if (_pathing3D != null)
                {
                    _pathing3D.Dispose();
                }                

                _pathing = value;
                InitPathingData();

                _pathing3D = new PathDisplay3D(_pathing);

                Model3D = new PathingVisual3D()
                {
                    Content = _pathing3D.Model,
                    Transform = Transform3D
                };

                if (_pathing.Nodes != null && _pathing.Nodes.Count > 0)
                {
                    OnObjectSelected(_pathing.Nodes.ElementAt(0));
                }

                CreateNodeCommand.RaiseCanExecuteChanged();
            }
        }

        public string DefaultFileName
        {
            get
            {
                return "untitled.path";
            }            
        }

        public DelegateCommand NewFileCommand
        {
            get
            {
                if (_newFileCommand == null)
                {
                    _newFileCommand = new DelegateCommand(x =>
                        {
                            //todo check if they want to save before destroying

                            PathingData = new Path();                            
                            CurrentFileName = DefaultFileName;
                            SaveFileCommand.RaiseCanExecuteChanged();
                        },
                        y =>
                        {
                            return true;
                        });                    
                }
                return _newFileCommand;
            }
        }

        public DelegateCommand OpenFileCommand
        {
            get
            {
                if (_openFileCommand == null)
                {
                    _openFileCommand = new DelegateCommand(x =>
                        {
                            var od = new OpenFileDialog();
                            od.Filter = PathingFileFilter;
                            if ((bool)od.ShowDialog())
                            {
                                try
                                {
                                    PathingData = Path.LoadFile(od.FileName);
                                    CurrentFileName = System.IO.Path.GetFileName(od.FileName);
                                    SaveFileCommand.RaiseCanExecuteChanged();
                                }
                                catch (Exception e)
                                {                            
                                    //message box - cannot open file
                                    System.Windows.MessageBox.Show("Could not open file: " + od.FileName + "," + e.Message);
                                }
                            }
                        },
                        y =>
                        {
                            return true;
                        });
                }
                return _openFileCommand;
            }
        }

        public DelegateCommand SaveFileCommand
        {
            get
            {
                if (_saveFileCommand == null)
                {
                    _saveFileCommand = new DelegateCommand(x =>
                        {
                            var sd = new SaveFileDialog();
                            sd.FileName = DefaultFileName;
                            sd.AddExtension = true;
                            sd.DefaultExt = PathingFileExtension;
                            sd.Filter = PathingFileFilter;                            
                            if ((bool)sd.ShowDialog())
                            {
                                try
                                {
                                    PathingData.SaveAsBinary(sd.FileName);
                                }
                                catch (Exception e)
                                {
                                    System.Windows.MessageBox.Show("Could not save file: " + sd.FileName + ", " + e.Message);
                                }
                            }
                        }, y =>
                        {
                            return PathingData != null;
                        });
                }

                return _saveFileCommand;
            }
        }

        public DelegateCommand DeleteNodeCommand
        {
            get
            {
                if (_deleteNodeCommand == null)
                {
                    _deleteNodeCommand = new DelegateCommand(x =>
                    {
                        _pathing.RemoveNode(SelectedNode);
                        SelectedNode = null;
                    },
                    y =>
                    {
                        return SelectedNode != null;
                    });
                }
                return _deleteNodeCommand;
            }
        }

        public DelegateCommand CreateNodeCommand
        {
            get
            {
                if (_createNodeCommand == null)
                {
                    _createNodeCommand = new DelegateCommand(x =>
                    {
                        var node = new Node(0.0, 0.0, 0.0 + ZAdjustment);
                        _pathing.AddNode(node);                                                
                        ConnectNode(node, SelectedNode);
                        SelectedNode = node;
                    },
                    y =>
                    {
                        return _pathing != null;
                    });
                }
                return _createNodeCommand;
            }
        }


        [Dependency("EQEmu.Transform")]
        public Transform3D Transform3D
        {
            get { return _transform; }
            set
            {
                _transform = value;
            }
        }

        public IEnumerable<Node> DisconnectedNodes
        {
            get
            {
                if (_pathing != null)
                {
                    return _pathing.Nodes.Where(x => x.Neighbors.Count == 0);
                }

                return null;
            }
        }

        public IEnumerable<Node> UnreachableNodes
        {
            get
            {
                if (_pathing != null && SelectedNode != null)
                {
                    return _pathing.Nodes.Where(x => !SelectedNode.ConnectedNodes.Contains(x));
                }
                else
                {
                    return null;
                }
            }
        }

        public IEnumerable<Node> Nodes
        {
            get
            {
                if (_pathing != null)
                {
                    return _pathing.Nodes;
                }
                else return null;
            }
        }

        public Node SelectedNode
        {
            get { return _selectedNode; }
            set
            {
                _selectedNode = value;
                OnPropertyChanged("SelectedNode");
                if (value != null)
                {
                    _modelVisual.Children.Remove(_cursor);
                    _cursor.Center = new Point3D(value.X, value.Y, value.Z);
                    _modelVisual.Children.Add(_cursor);

                    var point = new Point3D(value.X, value.Y, value.Z);
                    var look = Transform3D.Transform(point);

                    OnObjectSelected(value,MultipleSelections);
                    OnCameraChange(new CameraChangeEventArgs()
                    {
                        LookAtPosition = look
                    });
                }
                else
                {
                    if (_cursor != null && _modelVisual != null)
                    {
                        _modelVisual.Children.Remove(_cursor);
                    }
                }

                DeleteNodeCommand.RaiseCanExecuteChanged();
                OnPropertyChanged("UnreachableNodes");
            }
        }

        public IEnumerable<Node> MultipleSelections
        {
            get { return _multipleSelections; }
            private set
            {
                _multipleSelections = value;
                if (_multipleSelections != null && _multipleSelections.Count() > 0)
                {
                    if (SelectedNode == null || (SelectedNode != null && !_multipleSelections.Contains(SelectedNode)))
                    {
                        SelectedNode = _multipleSelections.ElementAt(0);
                    }

                    List<Point3D> points = new List<Point3D>();
                    if (SelectedNode != null)
                    {
                        points.Add(new Point3D(SelectedNode.X, SelectedNode.Y, SelectedNode.Z));
                    }
                    if (MultipleSelections != null && MultipleSelections.Count() > 0)
                    {
                        foreach (var n in MultipleSelections.Where(x => x != SelectedNode))
                        {
                            points.Add(new Point3D(n.X, n.Y, n.Z));
                        }
                    }

                    var point = Common.Functions.Center(points);
                    var look = Transform3D.Transform(point);

                    OnObjectSelected(SelectedNode, value);
                    OnCameraChange(new CameraChangeEventArgs()
                    {
                        LookAtPosition = look
                    });
                }
                OnPropertyChanged("MultipleSelections");
            }
        }

        public double ZAdjustment
        {
            get { return _zAdjustment; }
            set
            {
                _zAdjustment = value;
                OnPropertyChanged("ZAdjustment");
            }
        }

        public bool AutoConnect
        {
            get { return _autoConnect; }
            set
            {
                _autoConnect = value;
                OnPropertyChanged("AutoConnect");
            }
        }

        public bool WarpConnect
        {
            get { return _warpConnect; }
            set
            {
                _warpConnect = value;
                OnPropertyChanged("WarpConnect");
            }
        }

        public bool TwoWayConnect
        {
            get { return _twoWayConnect; }
            set
            {
                _twoWayConnect = value;
                OnPropertyChanged("TwoWayConnect");
            }
        }

        private void ConnectNode(Node node,Node selectedNode)
        {
            if (selectedNode == null) return;

            if (WarpConnect)
            {
                if (TwoWayConnect)
                {
                    node.ConnectToNodeAsWarp(selectedNode);
                    selectedNode.ConnectToNodeAsWarp(node);
                }
                else
                {
                    node.ConnectToNodeAsWarp(selectedNode);
                }
            }
            else
            {
                if (TwoWayConnect)
                {
                    node.ConnectToNodeTwoWay(selectedNode);
                }
                else
                {
                    node.ConnectToNode(selectedNode);
                }
            }
        }

        public void UserClicked(Point3D point, AppInformation info)
        {
            if (info.SelectedRibbonTab == null || (info.SelectedRibbonTab as PathingRibbonTab) == null) return;
            if (_pathing == null) return;

            var p = Transform3D.Transform(point);
            
            if ( (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) && SelectedNode != null)
            {
                var node = SelectedNode;
                node.X = p.X;
                node.Y = p.Y;
                node.Z = p.Z + ZAdjustment;
                SelectedNode = node;
            }
            else if ( (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && SelectedNode != null)
            {
                var node = _pathing.GetNearbyNode(p);
                if (node == null) return;
                ConnectNode(SelectedNode, node);
            }
            else if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
            {
                var node = new Node(p.X, p.Y, p.Z + ZAdjustment);
                _pathing.AddNode(node);
                if (AutoConnect)
                {
                    ConnectNode(node, SelectedNode);
                }
                SelectedNode = node;
            }
            else
            {
                if (info.World3DSelectionBox != null)
                {
                    var nodes = new List<Node>();
                    foreach (var n in _pathing.Nodes)
                    {
                        var tp = new Point3D(n.X, n.Y, n.Z);
                        tp = Transform3D.Transform(tp);
                        if (info.World3DSelectionBox(tp, 2.0))
                        {
                            nodes.Add(n);
                        }
                    }
                    MultipleSelections = nodes;
                }

                var node = _pathing.GetNearbyNode(p);
                if (node != null)
                {
                    SelectedNode = node;
                }
            }
        }

        protected virtual void Dispose(bool disposeAll)
        {
            if (_pathing3D != null)
            {
                _pathing3D.Dispose();
            }
            DisposePathingData();
        }

        public void Dispose()
        {
            Dispose(true);
        }


        public void ConstrainView(ViewConstraints constraints)
        {
            if (_pathing3D == null) return;

            _pathing3D.UpdateView(new EQEmu.Display3D.ViewConstraints()
            {
               MaxX = constraints.MaxX,
               MaxY = constraints.MaxY,
               MaxZ = constraints.MaxZ,
               MinX = constraints.MinX,
               MinY = constraints.MinY,
               MinZ = constraints.MinZ
            });
        }        
    }
}
